Program #3 - 3 Minute Countdown Timer

;-------------------------------------------------------------------------;
;      EGGTIMER.ASM       A 3 minute countdown timer for boiling eggs     ;
;-------------------------------------------------------------------------;

        LIST P=16F84           ;  tells which processor is used
        INCLUDE "p16f84.inc"   ;  defines various registers etc. Look it over.
        ERRORLEVEL -224        ;  suppress annoying message because of tris
        __CONFIG _PWRTE_ON & _LP_OSC & _WDT_OFF   ;  configuration switches

;-------------------------------------------------------------------------;
;               Here we set up the user defined registers                 ;
;-------------------------------------------------------------------------;
           CBLOCK H'0C'         ; first free register address is 12
               sec              ; keeps track of seconds
               sec10            ; keeps track of tens of seconds
               mins             ; keeps track of minutes
               w_temp           ; holds value of W during interrupt
               status_temp      ; holds value of STATUS during interrupt
               finflag          ; act as a flag to indicate end of countdown
               oldsec10         ; holds last displayed value of sec10
           ENDC

           ORG 0              ; start a program memory location zero

           goto main          ; jump over the interrupt routine

           ORG 4

;-------------------------------------------------------------------------;
; here is the interrupt routine, happens every second if GIE is enabled   ;
;-------------------------------------------------------------------------;
           movwf w_temp             ; save W
           swapf STATUS,W           ; save status                  
           movwf status_temp        ; without changing flags

           decf sec, f              ; decrement seconds register
           movlw H'FF'              ; check if underflow
           subwf sec, W             ; will give zero if sec = H'FF'
           btfss STATUS, Z          ; skip next instruction if underflow
           goto restore             ; no underflow, leave interrupt routine
           movlw 9                  ; change seconds register to 9
           movwf sec
           decf sec10, f            ; now we follow the same procedure ...
           movlw H'FF'              ; for the sec10 register
           subwf sec10, W              
           btfss STATUS, Z          ; skip if underflow
           goto restore             ; no underflow, leave
           movlw 5                  ; change sec10 register to 5
           movwf sec10
           decf mins, f             ; and decrement minutes register
           movlw H'FF'              ; check if ...
           subwf mins, W            ; an underflow of minutes ...
           btfss STATUS, Z          ; yes means the count is finished
           goto restore             ; no underflow
           incf finflag, f          ; set the finished flag
restore:  
           swapf status_temp,W      ; get original status back
           movwf STATUS             ; into status register
           swapf w_temp,f           ; old no flags trick again
           swapf w_temp,W           ; to restore W
           bcf INTCON,T0IF          ; clear the TMR0 interrupt flag
           retfie                   ; finished reset GIE

;-------------------------------------------------------------------------;
;                       This is the main program                          ;
;-------------------------------------------------------------------------;
main:

;-------------------------------------------------------------------------;
;              initialize the ports, set up interrupts etc:               ;
;-------------------------------------------------------------------------;
         movlw B'00000000'    ; all bits low in W
         tris PORTA           ; contents of W copied to PORT A ...
         movlw B'00010000'    ; RB4 input, all other output
         tris PORTB           ; and PORT B
         movlw B'00000100'    ; port B pull-ups active
                              ; prescalar assigned to TMR0 and set 1:32
         option               ; rolls over each second
         movlw B'00100000'    ; T0IE set, GIE not set yet... 
         movwf INTCON         ; in the interrupt register
         clrf PORTB           ; display 0
  
;-------------------------------------------------------------------------;
;                    initialize some other registers:                     ;
;-------------------------------------------------------------------------;
         clrf sec             ; start with sec = zero
         clrf sec10           ; and sec10 = zero
         clrf oldsec10        ; make oldsec10 the same
         movlw D'3'           ; and minutes = 3
         movwf mins           
         clrf finflag         ; clear the finished flag

;-------------------------------------------------------------------------;
;                     wait for pushbutton to start                        ;
;-------------------------------------------------------------------------;
         btfsc PORTB, 4       ; switch closed, (gives 0)?
         goto $ -1            ; not yet
                              ; switch has been detected closed
                              ; ( no debounce necessary )
         clrf TMR0            ; start with timer at zero
         bcf INTCON, T0IF     ; and make sure the interrupt flag is clear
         bsf INTCON, GIE      ; enable interrupts, countdown starts

;-------------------------------------------------------------------------;
;   This is the main loop that displays the time and checks if finished   ;
;-------------------------------------------------------------------------;
loop:                         ; now go into a loop displaying registers in..
                              ; sequence each time sec10 changes, (every
                              ; ten seconds), and checking for finished flag
         btfsc finflag, 0     ; skip next if finflag not set
         goto finished        ; time up
         movf oldsec10, W     ; check if sec10 has changed
         subwf sec10, W       ; zero flag set if sec10 is same as oldsec
         btfsc STATUS, Z      ; skip over if not the same
         goto loop            ; else keep checking
         movf sec10, W        ; replace oldsec10
         movwf oldsec10       ; making it equal to sec10
         movf mins, W         ; display minutes
         movwf PORTB          ; on LEDs
         call onesecond       ; for one second
         clrf PORTB           ; blank briefly
         call msec250
         movf sec10, W        ; now the same with sec10
         movwf PORTB          ; show 10's of seconds
         call onesecond       ; for one second
         clrf PORTB           ; blank
         goto loop


;-------------------------------------------------------------------------;
;           We come to this point when the countdown is over              ;
;-------------------------------------------------------------------------;
finished:
         movlw H'F'           ; turn on all leds indicating finish
         movwf PORTB
         goto $               ; go into an endless loop

;-------------------------------------------------------------------------;
;        Four calls to a delay for 250 millisecond = 1 second delay       ;
;-------------------------------------------------------------------------;
onesecond:                     ; a subroutine that delays for 1 seconds
         call msec250
         call msec250
         call msec250
         call msec250
         return

;-------------------------------------------------------------------------;
;              This subroutine delays for 250 milliseconds                ;
;-------------------------------------------------------------------------;
msec250:                       ; a subroutine to delay 250 msec
         movlw D'250'          ; W is changed but no separate register needed
nmsec:                         ; could call it here with # msec in W
         nop                   ; each nop is 0.122 milliseconds
         nop
         nop                   ; each total loop is 8 X 0.122 = 0.976 msec
         nop
         addlw H'FF'           ; same as subtracting 1 from W
         btfss STATUS, Z       ; skip if result is zero
         goto nmsec            ; this is  2 X 0.122 msec  
         return                ; back to calling point

         end                   ; end of program

3 Minute Countdown Timer

When you run the program the LEDs will be initially blank. When the pushbutton on RB4 is pressed the count will be given every 10 seconds, first by flashing minutes and then tens of seconds. The numbers are in BCD but you should be able to catch them. Seconds are not flashed because they are always zero. If tens of seconds is zero, it will not be seen either. Rather than counting down in binary and then converting to decimal, three registers mins, sec10 and sec are used to handle the numbers directly in decimal. All LEDs are turned on when the count is over.

Interrupts

Program # 2 used an interrupt flag, (T0IF), but didn't use interrupts. This program uses interrupts. When interrupt conditions are satisfied, everything stops, the program jumps to address 4 and execution continues from there. Instructions are executed until a 'retfie' instruction returns to where the interruption happened and carries on as before.

The interrupt routine shouldn't modify any registers or flags used in the main program. That is the reason for the complex set of instructions at the beginning and end of the isr, (interrupt subroutine). Both 'W' and STATUS are likely to be changed in the isr and must be saved. The original values will be restored before exiting the isr.

Saving of these registers must be done with instructions that don't affect any of the flags in STATUS. One of these is 'swapf' which exchanges the high and low 4 bits of a register. Another is 'movwf'. Look over how the registers are saved in swapped form at the beginning and restored with 'swapf' at the end of the interrupt routine.

This program uses a timer zero interrupt. The main program is busy flashing the LEDs to indicate times. At the same time there seems to be a program running in the background that is updating the three time registers every second.

This is done by having the regular program interrupted every time TMR0 rolls over. TMR0 is given a 1:32 prescalar so this happens every second. Three requirements have to be met to generate a TMR0 interrupt.

  1. In INTCON register, T0IE, (Timer 0 Interrupt Enable), has to be set.
  2. In INTCON register, GIE, (General Interrupt Enable), as to be set.
  3. In INTCON register, T0IF, (Timer 0 Interrupt Flag), has to be set, (goes high).

Notice that GIE is not set until the 'button' is pressed to start the countdown. Setting GIE then activates the count. GIE is disabled each time an interrupt occurs and is reset by 'retfie'. T0IF is reset by code in the interrupt routine itself. Notice also that the first instruction is 'goto main' which jumps over the isr at program memory location 4.

Register usage

There are many more user named registers used in this program than previously. Two of these are w_temp and status_temp which are use to save values of W and the STATUS register. Another user defined register is 'finflag' which is set when the countdown is finished. Actually, only one bit, (bit 0), of the register is used. Finflag transfers the fact that the countdown is complete from the interrupt routine where it is detected to the main routine where it is acted upon. The register oldsec10 holds the value of sec10 that was last displayed. Only when sec10 differs from oldsec10 is the display updated.

Updating the Count

The count is initially 3 minutes, 0 seconds. Each second the isr decrements this by one second. Underflow is checked by seeing if the digit has gone to H'FF'. If this happens, the digit is reset to a starting value and the next digit to the left is decremented. When the underflow travels through all three digits, the finished flag is set. 

ADDLW

We haven't seen the addlw instruction yet. It obviously adds a literal, (number), to W. But, in this case it is used to subtract 1 from W. You might be tempted to us 'sublw'. I have, and regretted it. 'sublw 3' does not subtract 3 from W, it subtracts W from 3! H'0FF' is the twos compliment value of '-1'. Adding twos compliment is the same as subtracting. The instruction occurs in the nmsec subroutine which introduces a way to provide a delay without using an extra register as we did before. This subroutine can also be entered at two points, msec250 to get a 1/4 second delay or at nmsec with the number of millisecs of delay put in W first. 

SUBWF

'subwf (register), W' is a new instruction in the interrupt routine. Notice that W is subtracted from f, not the other way around. In this case we are only looking for a zero so it doesn't make a difference. But, in many cases it might be important. Also, if the destination is W, the register f is not altered. 

BCF

BCF, ( clear a bit in the register f), provides a way to clear an individual bit in a register without changing other bits. There is also a BSF instruction to set individual bits.

Now its your turn

a mistake!

If you run the program you find that the 4 LEDs don't come on when the count reaches 0 but 10 seconds later. Can you figure out what is wrong and fix it?

add a speaker

The obvious addition to the program is a speaker to indicate when time is up. I find the easiest way to do this is to put a piezoelectric speaker directly between a port and +4.5V. The ones having a paper cone attached seem louder. A piezo speaker is actually a capacitor so a 100 ohm resistor in series is a good idea to limit the original surge current.

The code to drive the speaker would simply bring the port high, hold it for a millisecond, bring it low, hold it for a millisecond and repeat over and over. See if you can add a speaker at the point where all the LEDs are turned on.

display last 10 digits of countdown

Can you figure a way to have all of the last digits 9-0 displayed?

a variable starting value

A way to change the starting value might be interesting too. Say, if the battery were attached with the button not pressed the starting time would default to 3 minutes. If the button is pressed when power is supplied, the display starts flashing 1,2,3...etc until the button is released. The time at release would be the starting time in minutes. Could you write that program? Remember button debouncing because you would then have to wait for an additional press to start the countdown.

one button, many functions

It is also inconvinient to power-down and back up to restart the countdown. How about a press of the button at the end to turn off the LEDs. Yet another press would reload the starting count, (maybe display the minutes). Another press would begin the countdown. Can you make the changes?